﻿using System;
using System.Diagnostics;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.ServiceModel;
using System.ServiceModel.Dispatcher;
using ProLinq.Toolkit;
using ProLinq.Wcf.Configuration;
using ProLinq.Wcf.Description;
using ProLinq.Wcf.Linq.Expressions.Visitors;
using ProLinq.Wcf.Tracing;

namespace ProLinq.Wcf.Runtime
{
	class QueryableOperationInvoker : IOperationInvoker
	{
		private IOperationInvoker _originalInvoker;
		private Type _contractType;
		private Settings _settings;

		public QueryableOperationInvoker(IOperationInvoker originalInvoker, Type contractType, Settings settings)
		{
			this._originalInvoker = originalInvoker;
			this._contractType = contractType;
			this._settings = settings;
		}

		#region IOperationInvoker Members

		public object[] AllocateInputs()
		{
			return this._originalInvoker.AllocateInputs();
		}

		public object Invoke(object instance, object[] inputs, out object[] outputs)
		{
			var result = this._originalInvoker.Invoke(instance, inputs, out outputs);

			var expressionHeader =
				ExpressionMessageHeader.Load(OperationContext.Current.IncomingMessageHeaders, this._settings.ExpressionSerializer);
			if (expressionHeader == null)
			{
				TraceSources.WCF.TraceEvent(TraceEventType.Error, 0, "Could not read the expression header.");
				throw new FaultException("Expression soap header was expected.");
			}
			
			var expression = expressionHeader.Expression;
			var qGraph = (IQueryable)result;
			var replacer = new ServerServiceVisitor { ServiceType = this._contractType, ServiceInstance = instance };

			Expression modifiedExpression;
			try
			{
				modifiedExpression = replacer.Visit(expression);
			}
			catch (Exception exc)
			{
				TraceSources.WCF.TraceEvent(TraceEventType.Error, 0, "Error applying expression to source query.");
				TraceSources.WCF.TraceData(TraceEventType.Error, 0, exc);
				TraceSources.WCF.TraceData(TraceEventType.Error, 1, expression);
				throw;
			}

			try
			{
				var lambda = Expression.Lambda(modifiedExpression);
				var lambdaDelegate = lambda.Compile();
				var res = lambdaDelegate.DynamicInvoke();

				var queryResult = (QueryResult)Activator.CreateInstance(typeof(QueryResult<>).MakeGenericType(qGraph.ElementType));

				if (modifiedExpression.Type.IsEnumerable())
				{
					var enumType = res.GetType().GetEnumerableType();
					var toArrayMethod = typeof(Enumerable).GetMethod("ToArray", BindingFlags.Public | BindingFlags.Static)
														   .MakeGenericMethod(enumType);
					var array = toArrayMethod.Invoke(null, new[] { res });
					queryResult.Result = array;
				}
				else
				{
					queryResult.Result = res;
				}

				return queryResult;
			}
			catch (Exception exc)
			{
				TraceSources.WCF.TraceEvent(TraceEventType.Error, 0, "Error executing the modified query.");
				TraceSources.WCF.TraceData(TraceEventType.Error, 0, exc);
				TraceSources.WCF.TraceData(TraceEventType.Error, 2, modifiedExpression);
				throw;
			}
		}

		public IAsyncResult InvokeBegin(object instance, object[] inputs, AsyncCallback callback, object state)
		{
			throw new NotSupportedException();
		}

		public object InvokeEnd(object instance, out object[] outputs, IAsyncResult result)
		{
			throw new NotSupportedException();
		}

		public bool IsSynchronous
		{
			get { return true; }
		}

		#endregion
	}
}
